package com.nowcoder.topic.pointers.hard;

import java.util.ArrayList;

/**
 * NC401 K 个不同整数子数组
 * @author d3y1
 */
public class NC401 {
    /**
     * 代码中的类名、方法名、参数名已经指定，请勿修改，直接返回方法规定的值即可
     *
     * 相似 -> NC343 和大于等于K的最短子数组     [nowcoder]
     * 相似 -> NC41 最长无重复子数组            [nowcoder]
     * 相似 -> NC170 最长不含重复字符的子字符串   [nowcoder]
     * 相似 -> NC356 至多包含K种字符的子串       [nowcoder]
     * 相似 -> NC387 找到字符串中的异位词        [nowcoder]
     *
     * @param nums int整型ArrayList
     * @param k int整型
     * @return int整型
     */
    public int nowsubarray (ArrayList<Integer> nums, int k) {
        // return solution1(nums, k);
        return solution2(nums, k);
        // return solution3(nums, k);
    }

    /**
     * 双指针
     *
     * 恰好存在K个不同整数的连续子数组的个数 = 最多存在K个不同整数的连续子数组的个数 - 最多存在K−1个不同整数的连续子数组的个数
     *
     * @param nums
     * @param k
     * @return
     */
    private int solution1(ArrayList<Integer> nums, int k){
        return atMostKDistinctKinds(nums, k)-atMostKDistinctKinds(nums, k-1);
    }

    /**
     * 获取连续子数组个数: 最多存在K个不同整数
     * @param nums
     * @param k
     * @return
     */
    private int atMostKDistinctKinds(ArrayList<Integer> nums, int k){
        int n = nums.size();
        // 统计滑动窗口中不同整数的个数
        int[] cnt = new int[n+1];

        int result = 0;

        // 双指针 毛毛虫
        int left = 0;
        int right = 0;
        // 滑动窗口[left, right)中整数种数
        int kind = 0;
        int numL,numR;
        while(right < n){
            numR = nums.get(right);
            if(cnt[numR] == 0){
                kind++;
            }
            cnt[numR]++;
            right++;

            while(kind > k){
                numL = nums.get(left);
                cnt[numL]--;
                if(cnt[numL] == 0){
                    kind--;
                }
                left++;
            }

            // 以nums[right-1]为右边界(固定nums[right-1]) 向左统计最多存在K个不同整数的连续子数组的个数
            result += right-left;
        }

        return result;
    }

    /**
     * 双指针
     *
     * 恰好存在K个不同整数的连续子数组的个数 = 最多存在K个不同整数的连续子数组的个数 - 最多存在K−1个不同整数的连续子数组的个数
     *
     * 简化
     *
     * @param nums
     * @param k
     * @return
     */
    private int solution2(ArrayList<Integer> nums, int k){
        return getSubsAtMostKDistinctKinds(nums, k)-getSubsAtMostKDistinctKinds(nums, k-1);
    }

    /**
     * 获取连续子数组个数: 最多存在K个不同整数
     * @param nums
     * @param k
     * @return
     */
    private int getSubsAtMostKDistinctKinds(ArrayList<Integer> nums, int k){
        int n = nums.size();
        // 统计滑动窗口中不同整数的个数
        int[] cnt = new int[n+1];

        int result = 0;

        // 双指针 毛毛虫
        int left=0,right=0;
        // 滑动窗口[left, right)中整数种数
        int kind = 0;
        int numL,numR;
        while(right < n){
            numR = nums.get(right++);
            if(cnt[numR]++ == 0){
                kind++;
            }

            while(kind > k){
                numL = nums.get(left++);
                cnt[numL]--;
                if(cnt[numL] == 0){
                    kind--;
                }
            }

            // 以nums[right-1]为右边界(固定nums[right-1]) 向左统计最多存在K个不同整数的连续子数组的个数
            result += right-left;
        }

        return result;
    }

    /**
     * 双指针
     *
     * 恰好存在K个不同整数的连续子数组的个数 = 最多存在K个不同整数的连续子数组的个数 - 最多存在K−1个不同整数的连续子数组的个数
     *
     * 合并
     *
     * @param nums
     * @param k
     * @return
     */
    private int solution3(ArrayList<Integer> nums, int k){
        int n = nums.size();
        // 统计滑动窗口中不同整数的个数
        int[] cnt1 = new int[n+1];
        int[] cnt2 = new int[n+1];

        int result = 0;

        // 双指针 毛毛虫
        int left1=0,left2=0,right=0;
        // 滑动窗口[left1, right)和[left2, right)中整数种数
        int kind1=0,kind2=0;
        int numL1,numL2,numR;
        while(right < n){
            numR = nums.get(right++);
            if(cnt1[numR]++ == 0){
                kind1++;
            }
            if(cnt2[numR]++ == 0){
                kind2++;
            }

            while(kind1 > k){
                numL1 = nums.get(left1++);
                cnt1[numL1]--;
                if(cnt1[numL1] == 0){
                    kind1--;
                }
            }

            while(kind2 > k-1){
                numL2 = nums.get(left2++);
                cnt2[numL2]--;
                if(cnt2[numL2] == 0){
                    kind2--;
                }
            }

            // 以nums[right-1]为右边界(固定nums[right-1]) 向左统计 最多存在K个不同整数的连续子数组的个数与最多存在K-1个不同整数的连续子数组的个数之差
            // (right-left1)-(right-left2) = left2-left1
            result += left2-left1;
        }

        return result;
    }
}